#include"GUser.h"

GUser::GUser(QObject *parent, int sockfd) : QThread(parent), flist(this) {
	
	//setup friends list to recevie status updates
	connect(&flist, SIGNAL(forwardStatusSig(QString,QString)), 
					this, SLOT(receiveStatus(QString,QString)));
	
	//create socket
	sock = new QTcpSocket(this);
	//setup socket
	sock->setSocketDescriptor(sockfd);
	
	//setup signals coming from the socket
	connect(sock, SIGNAL(readyRead()), this, SLOT(readyRead()));
	connect(sock, SIGNAL(disconnected()), this, SLOT(disconnected()));
	connect(sock, SIGNAL(error(QAbstractSocket::SocketError)), this, 
			SLOT(sockError(QAbstractSocket::SocketError)));
	
	loggedin = false;
}

GUser::~GUser() {
	cleanUp();
	delete sock;
}

QString GUser::userName() {
	return name;
}

bool GUser::validPacket(QStringList &args) {
	int header = 0;
	
	//get the header of the packet, if it cannot be read in then the packet is
	//assumed to be corrupt
	if(args.size() >= 1) {
		bool ok = false;
		header = args[0].toInt(&ok);
		if(!ok) return false;
	}
	
	//verify that the length of the packet is as expected
	switch(header) {
		case accountlogin:		return accountlogin_s == args.size();
		case accountcreate:		return accountcreate_s == args.size();
		case accountlogout:		return accountlogout_s == args.size();
		case sendmessage:		return sendmessage_s == args.size();
		case servermessage:		return servermessage_s == args.size();
		case friendlist:		return friendlist_s == args.size();
		case friendadd:			return friendadd_s == args.size();
		case friendremove:		return friendremove_s == args.size();
		case friendmessage:		return friendmessage_s == args.size();
		case friendstatus:		return friendstatus_s == args.size();
		case transferstart:		return transferstart_s == args.size();
		case transferstop:		return transferstop_s == args.size();
		case transferresponse:	return transferresponse_s == args.size();
		case transferchunk:		return transferchunk_s == args.size();
		case squelchlist:		return squelchlist_s == args.size();
		case squelchadd:		return squelchadd_s == args.size();
		case squelchremove:		return squelchremove_s == args.size();
		case captchaget:		return captchaget_s == args.size();
		case captchaverify:		return captchaverify_s == args.size();
		case ping:				return ping_s == args.size();
		default:				return false;
	}
	return false;
}

void GUser::cleanUp() {
	updateStatus(tr("offline"));
	
	disconnect(this, SLOT(receiveStatus(QString,QString)));
	
	logoutAccount(this);
	
	closeAllMessages();
	closeAllTransfers();
	
	flist.save(FRIENDFILE(name));
	slist.save(SQUELCHFILE(name));
	
	cout<<"ffile: " <<FRIENDFILE(name).toStdString() <<endl;
	cout<<"sfile: " <<SQUELCHFILE(name).toStdString() <<endl;
	
	flist.clear();
	slist.clear();
	name = "";
	loggedin = false;
}

void GUser::run() {
	
	
	
	exec();
}

inline QString GUser::timeStamp() {
	return QDateTime::currentDateTime().toString(tr("dd/mm/yy hh:mm:ss:zzz:AP "));
}

void GUser::closeMessage(QString user) {
#ifdef DEBUG
	cout<<"closemessage: " <<name.toStdString() <<endl;
	cout<<"name: " <<user.toStdString() <<endl;
#endif
	int length = active.size();
	for(int i=0; i<length; i++) {
		if(active[i]->na == user) {
			active.removeAt(i);
			return;
		}
	}
}

void GUser::receiveMessage(QString user, QString msg) {
#ifdef DEBUG
	cout<<"receivemessage: " <<name.toStdString() <<endl;
	cout<<"message: " <<msg.toStdString() <<" from: " <<user.toStdString() <<endl;
#endif
	
	if(slist.notSquelched(user)) {
		QTextStream stream(sock);
		stream<<(int)sendmessage <<tr("`") <<user <<tr("`") <<msg <<tr("\n");
		
	}
}

void GUser::hostClosedTransfer(QString user, QString fn) {
	QTextStream stream(sock);
	stream<<(int)transferstop <<tr("`") <<user <<tr("`") <<fn <<tr("\n");
}

void GUser::closeTransfer(QString user, QString fn) {
#ifdef DEBUG
	cout<<"closefile: " <<name.toStdString() <<endl;
	cout<<"name: " <<user.toStdString() <<endl;
#endif
	int length = transfer.size();
	for(int i=0; i<length; i++) {
		if(transfer[i]->na == user && transfer[i]->fn == fn) {
			transfer.removeAt(i);
			return;
		}
	}
	GResponse resp(GResponse::etransferstopped);
	QTextStream stream(sock);
	stream<<(int)servermessage <<tr("`") <<resp.msgCode() <<tr("`") <<resp.getServerString() <<tr("\n");
}

void GUser::receiveFileChunk(QString user, QString fn, QString chunk) {
#ifdef DEBUG
	cout<<"receivechunk: " <<name.toStdString() <<endl;
	cout<<"message from: " <<user.toStdString() <<endl;
#endif
	QTextStream stream(sock);
	stream<<(int)transferchunk <<tr("`") <<user <<tr("`") <<chunk <<tr("\n");
}

void GUser::requestTransferResponse(QString user, QString fn, bool r) {
#ifdef DEBUG
	
#endif
	int length = transfer.size();
	for(int i=0; i<length; i++) {
		if(transfer[i]->na == user && transfer[i]->fn == fn) break;
		if((i-1) == length) return;
	}
	
	QTextStream stream(sock);
	stream<<(int)transferstart <<tr("`") <<user <<tr("`") <<fn <<tr("`") <<r <<tr("\n");
	
}

void GUser::requestFileTransfer(QString user, QString fn, int size) {
	QTextStream stream(sock);
	stream<<(int)transferstart <<tr("`") <<user <<tr("`") <<fn <<tr("`") <<size <<tr("\n");
}

GResponse GUser::initializeFileTransfer(QString user, QString fn, int size) {
#ifdef DEBUG
	cout<<"initializefile: " <<name.toStdString() <<endl;
	cout<<"name: " <<user.toStdString() <<endl;
#endif
	GFileMap* tmpfile = new GFileMap;
	tmpfile->na = user;
	tmpfile->fn = fn;
	GResponse resp = setupFileTransferChannel(tmpfile, this, user);
	if(resp.msgCode() == GResponse::enone) {
		transfer.push_back(tmpfile);
		tmpfile->requestTheTransfer(this->name, fn, size);
	} else {
		delete tmpfile;
	}
	return resp;
}

void GUser::sendChunk(QString user, QString chunk) {
#ifdef DEBUG
	cout<<"sendfile: " <<name.toStdString() <<endl;
	cout<<"to: " <<user.toStdString() <<endl;
#endif
	int length = transfer.size();
	for(int i=0; i<length; i++) {
		//found the user so send him the message
		if(transfer[i]->na == user) {
			transfer[i]->sendTheChunk(user, chunk);
			return;
		}
	}
}

void GUser::closeAllTransfers() {
#ifdef DEBUG
	cout<<"closeallfiles: " <<name.toStdString() <<endl;
	cout<<"total: " <<transfer.size() <<endl;
#endif
	int length = transfer.size();
	for(int i=length-1; i>=0; i--) {
		transfer[i]->closeTheTransfer(transfer[i]->na, transfer[i]->fn);
		transfer.pop_back();
	}
}

void GUser::closeATransfer(QString user, QString fn) {
	int length = transfer.size();
	for(int i=0; i<length; i++) {
		//if the host is closing he transfer then find it in the filemap and close it
		if(transfer[i]->na == user && transfer[i]->fn == fn) {
			transfer[i]->closeTheTransfer(user, fn);
			transfer.removeAt(i);
			return;
		}
	}
	
	//if the receiver is closing the transfer then emit the message to close it
	emit stopFileTransfer(user, fn);
	
}

void GUser::disconnected() {
#ifdef DEBUG
	cout<<"GUser disconnected: " <<name.toStdString() <<endl;
#endif
	if(loggedin)
		cleanUp();
	
	exit(0);
}

void GUser::readyRead() {
	QTextStream stream(sock);
	QString raw;
	
	//read in the packet from the socket stream
	char ch = 0;
	while(ch != '\n') {
		stream>>ch;
		raw.push_back(ch);
	}
	
	//chop off the traling newline character
	raw.chop(1);
	
	//split the packet up to its elements
	QStringList args = raw.split(tr("`"), QString::KeepEmptyParts);
	
	//validate the packet
	if(!validPacket(args)) {
#ifdef DEBUG
		cout<<"GUser bad packet" <<endl;
		cout<<"raw: " <<raw.toStdString() <<endl;
#endif
		GResponse reply(GResponse::einvalidpacket);
		stream<<(int)servermessage <<tr("`") <<reply.msgCode() <<tr("`") <<reply.getServerString() <<tr("\n");
		
		//read out all the data in the buffer and delete it
		QByteArray dump = sock->readAll();
		return;
	}
	
	//get the header as an integer so it can be used in the switch statement
	int header = args[0].toInt();
	
#ifdef DEBUG
	cout<<"GUser header: " <<debugHeader((packetType)header).toStdString() <<endl;
#endif
	
	if(header == accountlogin) {
		if(!loggedin) {
			
			GResponse result = emit loginAccount(this, args[1], args[2]);
			
#ifdef DEBUG
			cout<<"GUser result: " <<result.msgCode() <<endl;
			cout<<"GUser result: " <<result.getString() <<endl;
			
			cout<<"GUser username: " <<args[1].toStdString() <<" password: " <<args[2].toStdString() <<endl;
#endif
			
			stream<<(int)servermessage <<tr("`") <<result.msgCode() <<tr("\n");
			
			if(result.msgCode() == GResponse::enone) {
				loggedin = true;
				//do some login setup here
				//
				name = args[1];
				
				flist.load(FRIENDFILE(name));
				
				//link up to receive friend list status updates
				int length = flist.size();
				for(int i=0; i<length; i++) {
					GFriend* tmp = flist[i];
					tmp->index(linkFriendList(this, tmp, tmp->index(), tmp->name()));
					
				}
				flist.setChanged();
				
				slist.load(SQUELCHFILE(name));
				
				updateStatus(tr("online"));
				//end login setup
				//
				
			}
			if(result.msgCode() == GResponse::ealreadyloggedin) {
				exit(0);
			}
			return;
		} else {
			//already logged in, do something
			//
#ifdef DEBUG
			cout<<"GUser: " <<name.toStdString() <<" already logged in" <<endl;
#endif
			return;
		}
	}
	
	//the user is not allowed to do anything until he logs in
	if(!loggedin) {
		switch(header) {
			case captchaget: {
#ifdef DEBUG
				cout<<"GUser captchaget: " <<args[1].toStdString() <<endl;
#endif	
				
				QString tmpname = args[1];
				
				//check to see if the captcha file exist,
				//if they don't then the account has already been verified
				QString fn(tr("USERS/")+name+tr("/captcha.jpg"));
				if(!QFile::exists(fn)) {
					GResponse reply(GResponse::everified);
					stream<<(int)servermessage <<tr("`") <<reply.msgCode() <<tr("`") <<reply.getServerString() <<tr("\n");
					return;
				}
				
				//read in the data from the file
				QFile imgseq(fn);
				imgseq.open(QFile::ReadOnly);
				
				//pack the image data into a bytearray and mime encode it
				QByteArray imgdata(imgseq.readAll());
				QByteArray b64 = imgdata.toBase64();
				
				stream<<(int)captchaget <<tr("`") <<tmpname <<tr("`") <<b64 <<tr("\n");
				
				
				return;
			}
			case captchaverify: {
#ifdef DEBUG
				cout<<"GUser captchaverify: " <<args[1].toStdString() <<" : " <<args[2].toStdString() <<endl;
#endif
				QString tmpname = args[1];
				QString tmpseq = args[2];
				
				//check to see if the captcha file exist,
				//if they don't then the account has already been verified
				QString fn(tr("USERS/")+name+tr("/captcha.txt"));
				if(!QFile::exists(fn)) {
					GResponse reply(GResponse::everified);
					stream<<(int)servermessage <<tr("`") <<reply.msgCode() <<tr("`") <<reply.getServerString() <<tr("\n");
					return;
				}
				
				//read in the str sequence from the file
				QFile strseq(fn);
				strseq.open(QFile::ReadOnly);
				QTextStream verstream(&strseq);
				QString fileseq;
				verstream>>fileseq;
				strseq.close();
				
				//if the string sequence is not valid return th error
				if(tmpseq != fileseq) {
					stream<<(int)captchaverify <<tr("`") <<tmpname <<tr("`") <<0 <<tr("\n");
					return;
				}
				
				//the string sequence is valid so tell GLogin to verify the account
				emit setAccountVerified(tmpname);
				
				
				QString fndel(tr("rm ")+fn);
				QString fnimagedel(tr("rm ")+tr("USERS/")+name+tr("/captcha.jpg"));
				
				system(fndel.toAscii());
				system(fnimagedel.toAscii());
				
				stream<<(int)captchaverify <<tr("`") <<tmpname <<tr("`") <<1 <<tr("\n");
				
			return;	
			}
		case accountcreate: {
				GResponse reply = emit addAccount(args[1], args[2], "blank@blank.com");
#ifdef DEBUG
				cout<<"GUser createaccount: " <<reply.msgCode() <<" string: " <<reply.getServerString().toStdString() <<endl;
				cout<<"GUser name: " <<args[1].toStdString() <<" password: " <<args[2].toStdString() <<endl;
#endif
				stream<<(int)servermessage <<tr("`") <<reply.msgCode() <<tr("`") <<reply.getServerString() <<tr("\n");
				return;
			}
			
		}
		
		
		
		GResponse reply(GResponse::enotloggedin);
		stream<<(int)servermessage <<tr("`") <<reply.msgCode() <<tr("`") <<reply.getServerString() <<tr("\n");
		return;
	}
	
	//when the user wants to do something handle the request
	switch(header) {
		case ping: {
#ifdef DEBUG
			cout<<name.toStdString() <<" pinged" <<endl;
#endif
			stream<<(int)ping <<tr("\n");
			break;
		}
		case friendlist: {
			QString retlist;// = flist.listCSV();
			
			int flength = flist.size();
			for(int i=0; i<flength; i++) {
				retlist.append(flist[i]->name());
				retlist.append("`");
				if(slist.notSquelched(flist[i]->name())) {
					retlist.append(flist[i]->status());
				} else {
					retlist.append("squelched");
				}
				retlist.append("`");
			}
			
			
#ifdef DEBUG
				cout<<name.toStdString() <<" friend list" <<endl;
				cout<<"list: " <<retlist.toStdString() <<endl;
#endif
			stream<<(int)friendlist <<tr("`") <<retlist <<tr("\n");
			sock->flush();
			break;
		}
		case friendadd: {
			QString sname(args[1]);
#ifdef DEBUG
			cout<<name.toStdString() <<" friend add " <<sname.toStdString() <<endl;
#endif
			GFriend* tmp = flist.add(sname);
			if(tmp) linkFriendList(this, tmp, tmp->index(), tmp->name());
			stream<<(int)friendadd <<tr("`") <<sname <<tr("\n");
			break;
		}
		case friendremove: {
#ifdef DEBUG
			cout<<name.toStdString() <<" friend remove " <<args[1].toStdString() <<endl;
#endif
			flist.remove(args[1]);
			stream<<(int)friendremove <<tr("`") <<args[1] <<tr("\n");
			break;
		}
		case squelchlist: {
			QString retlist = slist.listCSV();
#ifdef DEBUG
				cout<<name.toStdString() <<" squelch list" <<endl;
				cout<<"list: " <<retlist.toStdString() <<endl;
#endif
			stream<<(int)squelchlist <<tr("`") <<retlist <<tr("\n");
			break;
		}
		case squelchadd: {
			QString sname = args[1];
#ifdef DEBUG
				cout<<name.toStdString() <<" squelch add " <<sname.toStdString() <<endl;
#endif
			slist.add(sname);
			stream<<(int)squelchadd <<tr("`") <<sname <<tr("\n");
			break;
		}
		case squelchremove: {
			QString sname = args[1];
#ifdef DEBUG
				cout<<name.toStdString() <<" squelch remove " <<sname.toStdString() <<endl;
#endif
			slist.remove(sname);
			stream<<(int)squelchremove <<tr("`") <<sname <<tr("\n");
			break;
		}
		case sendmessage: {
#ifdef DEBUG
			cout<<name.toStdString() <<" sending message: " <<args[2].toStdString() <<" to: " <<args[1].toStdString() <<endl;
#endif
			
			sendMessage(args[1], args[2]);
			break;
		}
		case accountlogout: {
				
#ifdef DEBUG
				cout<<"GUser logging out: " <<name.toStdString() <<endl;
#endif
				cleanUp();
				
				stream<<(int)accountlogout <<tr("\n");
			break;
		}
		case transferstart: {
#ifdef DEBUG
			cout<<"GUser initiatetransfer: " <<name.toStdString() <<endl;
#endif
			GResponse resp = initializeFileTransfer(args[1], args[2], args[3].toInt());
			
			stream<<(int)servermessage <<tr("`") <<resp.msgCode() <<tr("`") <<resp.getServerString() <<tr("\n");
			
			break;
		}
	case transferresponse: {
#ifdef DEBUG
		cout<<"GUser transfer response: " <<name.toStdString() <<endl;
#endif
		QString tmpuser = args[1];
		QString tmpfn = args[2];
		bool ack = args[3].toInt();
		
		int length = transfer.size();
		for(int i=0; i<length; i++) {
			if(transfer[i]->na == tmpuser && transfer[i]->fn == tmpfn) {
				transfer[i]->approved = ack;
			}
		}
		
		break;
	}
	case transferchunk: {
#ifdef DEBUG
			cout<<"GUser filechunk: " <<name.toStdString() <<endl;
#endif
		QString tmpname = args[1];
		QString tmpfn = args[2];
		QString chunk = args[3];
		
		int length = transfer.size();
		for(int i=0; i<length; i++) {
			if(transfer[i]->na == tmpname && transfer[i]->fn == tmpfn) {
				transfer[i]->sendTheChunk(this->name, chunk);
			}
		}
		
		break;
		}
	case transferstop: {
#ifdef DEBUG
		
#endif
		QString tmpuser = args[1];
		QString tmpfn = args[2];
		
		int length = transfer.size();
		for(int i=0; i<length; i++) {
			if(transfer[i]->na == tmpuser && transfer[i]->fn == tmpfn) {
				transfer.removeAt(i);
				return;
			}
		}
		
		break;	
		}
		default: {
			
		}
	}//end switch
	
	
	
}//end of ReadyRead

void GUser::sockError(QAbstractSocket::SocketError e) {
#ifdef DEBUG
	cout<<"GUser: " <<name.toStdString() <<" generated socket error: " <<(int)e <<endl;
#endif
	if(loggedin)
		cleanUp();
	
	exit(0);
}


QString GUser::debugHeader(packetType pack) {
	switch(pack) {
		case accountlogin: return tr("LOGIN");
		case accountcreate: return tr("ACCOUNT CREATE");
		case sendmessage: return tr("SEND MESSAGE");
		case servermessage: return tr("SERVER MESSAGE");
		case accountlogout: return tr("logout");
		case friendlist: return tr("FRIEND LIST");
		case friendadd: return tr("FRIEND ADD");
		case friendremove: return tr("FRIEND REMOVE");
		case friendmessage: return tr("FRIEND MESSAGE");
		case squelchlist: return tr("SQUELCH LIST");
		case squelchadd: return tr("SQUELCH ADD");
		case squelchremove: return tr("SQUELCH REMOVE");
		case ping: return tr("PING");
		default: tr("UNKNOWN HEADER");
	}
	return tr("UNKNOWN HEADER");
}

void GUser::sendMessage(QString user, QString msg) {
#ifdef DEBUG
	cout<<"sendmessage: " <<name.toStdString() <<endl;
	cout<<"to: " <<user.toStdString() <<" msg: " <<msg.toStdString() <<endl;
#endif
	int length = active.size();
	for(int i=0; i<length; i++) {
		//found the user so send him the message
		if(active[i]->na == user) {
#ifdef DEBUG
			cout<<"sending to active: " <<user.toStdString() <<endl;
#endif
			active[i]->sendTheMessage(name, msg);
			return;
		}
	}
#ifdef DEBUG
	cout<<"adding new person to chat list: " <<user.toStdString() <<endl;
#endif
	//if the user is not in the list add him to it
	GResponse resp = initializeMessage(user);
	if(!resp.msgCode()) {
		active[active.size()-1]->sendTheMessage(name, msg);
	} else {
		//report error to user
		QTextStream stream(sock);
		stream<<(int)servermessage <<tr("`") <<resp.msgCode() <<tr("`") <<resp.getServerString() <<tr("\n");
	}
}

void GUser::closeAllMessages() {
#ifdef DEBUG
	cout<<"closeallmessages: " <<name.toStdString() <<endl;
	cout<<"total: " <<active.size() <<endl;
#endif
	int length = active.size();
	for(int i=0; i<length; i++) {
		active[active.size()-1]->closeTheMessage(name);
		active.pop_back();
	}
}

GResponse GUser::initializeMessage(QString user) {
#ifdef DEBUG
	cout<<"initializemessage: " <<name.toStdString() <<endl;
	cout<<"name: " <<user.toStdString() <<endl;
#endif
	GChatMap* tmpmsg = new GChatMap;
	tmpmsg->na = user;
	GResponse resp = setupMessageChannel(tmpmsg, user);
	if(resp.msgCode() == GResponse::enone) {
		active.push_back(tmpmsg);
	} else {
		delete tmpmsg;
	}
	return resp;
}

void GUser::receiveStatus(QString name, QString status) {
#ifdef DEBUG
	cout<<"recevied status: " <<this->name.toStdString() <<endl;
	cout<<"status: " <<status.toStdString() <<" from: " <<name.toStdString() <<endl;
#endif
	
	QTextStream stream(sock);
	stream<<(int)friendstatus <<tr("`") <<name <<tr("`") <<status <<tr("\n");
}
